import { CairoInt16, constants } from '../../../src';

const { PRIME } = constants;

describe('CairoInt16 class Unit Tests', () => {
  describe('constructor with different input types', () => {
    test('should handle positive number input', () => {
      const i16 = new CairoInt16(1000);
      expect(i16.data).toBe(1000n);
    });

    test('should handle negative number input', () => {
      const i16 = new CairoInt16(-1000);
      expect(i16.data).toBe(-1000n);
    });

    test('should handle bigint input', () => {
      const i16 = new CairoInt16(12345n);
      expect(i16.data).toBe(12345n);
    });

    test('should handle negative bigint input', () => {
      const i16 = new CairoInt16(-12345n);
      expect(i16.data).toBe(-12345n);
    });

    test('should handle zero values', () => {
      const i16FromNumber = new CairoInt16(0);
      const i16FromBigint = new CairoInt16(0n);

      expect(i16FromNumber.data).toBe(0n);
      expect(i16FromBigint.data).toBe(0n);
    });

    test('should handle maximum i16 value', () => {
      const maxI16 = 32767n;
      const i16 = new CairoInt16(maxI16);
      expect(i16.data).toBe(maxI16);
    });

    test('should handle minimum i16 value', () => {
      const minI16 = -32768n;
      const i16 = new CairoInt16(minI16);
      expect(i16.data).toBe(minI16);
    });
  });

  describe('validation', () => {
    test('should accept valid i16 values', () => {
      expect(() => new CairoInt16(-32768)).not.toThrow();
      expect(() => new CairoInt16(0)).not.toThrow();
      expect(() => new CairoInt16(32767)).not.toThrow();
      expect(() => new CairoInt16('A')).not.toThrow(); // UTF-8 encoded to 65
      expect(() => new CairoInt16(1000n)).not.toThrow();
      expect(() => new CairoInt16(-1000n)).not.toThrow();
    });

    test('should reject values less than -32768', () => {
      expect(() => new CairoInt16(-32769)).toThrow('Value is out of i16 range [-32768, 32767]');
      expect(() => new CairoInt16(-40000n)).toThrow('Value is out of i16 range [-32768, 32767]');
      // Note: large negative string values get UTF-8 encoded to large positive values
    });

    test('should reject values greater than 32767', () => {
      expect(() => new CairoInt16(32768)).toThrow('Value is out of i16 range [-32768, 32767]');
      expect(() => new CairoInt16(40000n)).toThrow('Value is out of i16 range [-32768, 32767]');
      expect(() => new CairoInt16('40000')).toThrow('Value is out of i16 range [-32768, 32767]');
    });

    test('should handle valid string inputs correctly', () => {
      const i16FromCharString = new CairoInt16('A'); // UTF-8 encoded to 65
      const i16FromNumString = new CairoInt16('100'); // Parsed as number 100
      const i16FromHexString = new CairoInt16('0x7fff');

      expect(i16FromCharString.data).toBe(65n); // ASCII value of 'A'
      expect(i16FromNumString.data).toBe(100n); // Parsed as number
      expect(i16FromHexString.data).toBe(32767n);
    });

    test('should reject decimal numbers', () => {
      expect(() => new CairoInt16(42.5)).toThrow(
        'Invalid input: decimal numbers are not supported, only integers'
      );
      expect(() => new CairoInt16(-1.1)).toThrow(
        'Invalid input: decimal numbers are not supported, only integers'
      );
    });

    test('should handle edge cases and invalid inputs', () => {
      expect(() => CairoInt16.validate(null as any)).toThrow('Invalid input: null or undefined');
      expect(() => CairoInt16.validate(undefined as any)).toThrow(
        'Invalid input: null or undefined'
      );
      expect(() => CairoInt16.validate({} as any)).toThrow(
        'Invalid input: objects are not supported'
      );
      expect(() => CairoInt16.validate([] as any)).toThrow(
        'Invalid input: objects are not supported'
      );
    });
  });

  describe('toBigInt method', () => {
    test('should return the stored bigint value', () => {
      const values = [-32768, -1000, 0, 1000, 32767];
      values.forEach((val) => {
        const i16 = new CairoInt16(val);
        expect(i16.toBigInt()).toBe(BigInt(val));
      });
    });

    test('should handle negative values', () => {
      const i16 = new CairoInt16(-12345);
      expect(i16.toBigInt()).toBe(-12345n);
    });

    test('should handle boundary values', () => {
      const minI16 = new CairoInt16(-32768);
      const maxI16 = new CairoInt16(32767);
      expect(minI16.toBigInt()).toBe(-32768n);
      expect(maxI16.toBigInt()).toBe(32767n);
    });
  });

  describe('toHexString method', () => {
    test('should convert zero to hex', () => {
      const i16 = new CairoInt16(0);
      expect(i16.toHexString()).toBe('0x0');
    });

    test('should convert positive numbers to hex', () => {
      const i16 = new CairoInt16(255);
      expect(i16.toHexString()).toBe('0xff');
    });

    test('should convert negative numbers to hex using field element representation', () => {
      const i16 = new CairoInt16(-1);
      // -1 becomes PRIME + (-1) = PRIME - 1
      const fieldElement = PRIME - 1n;
      expect(i16.toHexString()).toBe(`0x${fieldElement.toString(16)}`);
    });

    test('should convert boundary values to hex', () => {
      const minI16 = new CairoInt16(-32768);
      const maxI16 = new CairoInt16(32767);
      const minFieldElement = PRIME - 32768n;
      expect(minI16.toHexString()).toBe(`0x${minFieldElement.toString(16)}`);
      expect(maxI16.toHexString()).toBe('0x7fff');
    });
  });

  describe('decodeUtf8 method', () => {
    test('should decode positive values correctly', () => {
      const i16 = new CairoInt16(65); // 'A'
      expect(i16.decodeUtf8()).toBe('A');
    });

    test("should handle negative values with two's complement", () => {
      const i16 = new CairoInt16(-1);
      // Negative values are converted using 2^16 + value for UTF-8 decoding
      expect(typeof i16.decodeUtf8()).toBe('string');
    });

    test('should handle boundary values', () => {
      const minI16 = new CairoInt16(-32768);
      const maxI16 = new CairoInt16(32767);
      expect(typeof minI16.decodeUtf8()).toBe('string');
      expect(typeof maxI16.decodeUtf8()).toBe('string');
    });
  });

  describe('validate static method', () => {
    test('should validate correct number inputs', () => {
      expect(() => CairoInt16.validate(-32768)).not.toThrow();
      expect(() => CairoInt16.validate(0)).not.toThrow();
      expect(() => CairoInt16.validate(32767)).not.toThrow();
    });

    test('should validate correct bigint inputs', () => {
      expect(() => CairoInt16.validate(-32768n)).not.toThrow();
      expect(() => CairoInt16.validate(0n)).not.toThrow();
      expect(() => CairoInt16.validate(32767n)).not.toThrow();
    });

    test('should reject out-of-range values', () => {
      expect(() => CairoInt16.validate(-32769)).toThrow(
        'Value is out of i16 range [-32768, 32767]'
      );
      expect(() => CairoInt16.validate(32768)).toThrow('Value is out of i16 range [-32768, 32767]');
      expect(() => CairoInt16.validate(-40000n)).toThrow(
        'Value is out of i16 range [-32768, 32767]'
      );
      expect(() => CairoInt16.validate(40000n)).toThrow(
        'Value is out of i16 range [-32768, 32767]'
      );
    });

    test('should reject invalid types', () => {
      expect(() => CairoInt16.validate(null as any)).toThrow('Invalid input: null or undefined');
      expect(() => CairoInt16.validate(undefined as any)).toThrow(
        'Invalid input: null or undefined'
      );
      expect(() => CairoInt16.validate({} as any)).toThrow(
        'Invalid input: objects are not supported'
      );
      expect(() => CairoInt16.validate([] as any)).toThrow(
        'Invalid input: objects are not supported'
      );
    });
  });

  describe('is static method', () => {
    test('should return true for valid inputs', () => {
      expect(CairoInt16.is(-32768)).toBe(true);
      expect(CairoInt16.is(0)).toBe(true);
      expect(CairoInt16.is(32767)).toBe(true);
      expect(CairoInt16.is(-1000n)).toBe(true);
      expect(CairoInt16.is('A')).toBe(true); // UTF-8 encoded to 65
      expect(CairoInt16.is('100')).toBe(true); // Parsed as number 100
    });

    test('should return false for invalid inputs', () => {
      expect(CairoInt16.is(-32769)).toBe(false);
      expect(CairoInt16.is(32768)).toBe(false);
      expect(CairoInt16.is(null as any)).toBe(false);
      expect(CairoInt16.is(undefined as any)).toBe(false);
      expect(CairoInt16.is({} as any)).toBe(false);
      expect(CairoInt16.is(42.5)).toBe(false);
    });
  });

  describe('isAbiType static method', () => {
    test('should identify correct ABI type', () => {
      expect(CairoInt16.isAbiType('core::integer::i16')).toBe(true);
      expect(CairoInt16.isAbiType('core::integer::i8')).toBe(false);
      expect(CairoInt16.isAbiType('core::integer::u16')).toBe(false);
      expect(CairoInt16.isAbiType('felt252')).toBe(false);
    });
  });

  describe('signed integer specific tests', () => {
    test('should handle negative values correctly', () => {
      const negativeValues = [-32768, -12345, -1000, -1];
      negativeValues.forEach((val) => {
        const i16 = new CairoInt16(val);
        expect(i16.data).toBe(BigInt(val));
        expect(i16.toBigInt()).toBe(BigInt(val));
      });
    });

    test("should handle two's complement boundary correctly", () => {
      const minI16 = new CairoInt16(-32768);
      const maxI16 = new CairoInt16(32767);

      expect(minI16.data).toBe(-32768n);
      expect(maxI16.data).toBe(32767n);

      // Test that values outside range are rejected
      expect(() => new CairoInt16(-32769)).toThrow();
      expect(() => new CairoInt16(32768)).toThrow();
    });

    test('should maintain sign consistency', () => {
      const testCases = [
        { input: -12345, expected: -12345n },
        { input: 12345, expected: 12345n },
        { input: 'A', expected: 65n }, // UTF-8 encoded
        { input: '5', expected: 5n }, // Parsed as number
        { input: -1n, expected: -1n },
        { input: 1n, expected: 1n },
      ];

      testCases.forEach(({ input, expected }) => {
        const i16 = new CairoInt16(input as any);
        expect(i16.data).toBe(expected);
        expect(i16.toBigInt()).toBe(expected);
      });
    });
  });

  describe('toApiRequest method', () => {
    test('should return hex string array for zero', () => {
      const i16 = new CairoInt16(0);
      const result = i16.toApiRequest();
      expect(result).toEqual(['0x0']);
      expect(result).toHaveProperty('__compiled__', true);
    });

    test('should return hex string array for positive numbers', () => {
      const i16 = new CairoInt16(1000);
      const result = i16.toApiRequest();
      expect(result).toEqual(['0x3e8']);
      expect(result).toHaveProperty('__compiled__', true);
    });

    test('should return field element hex representation for negative numbers', () => {
      const i16 = new CairoInt16(-1000);
      const result = i16.toApiRequest();
      // Negative value -1000 becomes PRIME + (-1000) = PRIME - 1000
      const fieldElement = PRIME - 1000n;
      const expectedValue = `0x${fieldElement.toString(16)}`;
      expect(result).toEqual([expectedValue]);
      expect(result).toHaveProperty('__compiled__', true);
    });

    test('should handle boundary values', () => {
      const minI16 = new CairoInt16(-32768);
      const maxI16 = new CairoInt16(32767);
      const minFieldElement = PRIME - 32768n;
      const expectedMinValue = `0x${minFieldElement.toString(16)}`;
      expect(minI16.toApiRequest()).toEqual([expectedMinValue]);
      expect(maxI16.toApiRequest()).toEqual(['0x7fff']);
    });
  });

  describe('factoryFromApiResponse method', () => {
    test('should create CairoInt16 from API response iterator', () => {
      const mockIterator = {
        next: jest.fn().mockReturnValue({ value: '0x3e8', done: false }),
      };
      const i16 = CairoInt16.factoryFromApiResponse(mockIterator as any);
      expect(i16.data).toBe(1000n);
    });

    test('should handle positive values from API response', () => {
      const mockIterator = {
        next: jest.fn().mockReturnValue({ value: '1000', done: false }),
      };
      const i16 = CairoInt16.factoryFromApiResponse(mockIterator as any);
      expect(i16.data).toBe(1000n);
    });

    test('should handle boundary values from API response', () => {
      const mockIterator = {
        next: jest.fn().mockReturnValue({ value: '32767', done: false }),
      };
      const i16 = CairoInt16.factoryFromApiResponse(mockIterator as any);
      expect(i16.data).toBe(32767n);
    });
  });

  describe('round-trip consistency', () => {
    test('should maintain consistency between constructor types', () => {
      const testValues = [-32768, -1000, 0, 1000, 32767];
      testValues.forEach((val) => {
        const i16FromNumber = new CairoInt16(val);
        const i16FromBigint = new CairoInt16(BigInt(val));
        // Skip string comparison as strings are UTF-8 encoded and produce different values

        expect(i16FromNumber.toBigInt()).toBe(i16FromBigint.toBigInt());
      });
    });

    test('should handle round-trip conversions', () => {
      const originalValue = -12345;
      const i16 = new CairoInt16(originalValue);
      const bigintValue = i16.toBigInt();
      const newI16 = new CairoInt16(bigintValue);

      expect(newI16.toBigInt()).toBe(BigInt(originalValue));
      expect(newI16.data).toBe(i16.data);
    });
  });

  describe('edge cases and consistency checks', () => {
    test('should handle boundary values correctly', () => {
      const minI16 = new CairoInt16(-32768);
      const maxI16 = new CairoInt16(32767);

      expect(minI16.data).toBe(-32768n);
      expect(maxI16.data).toBe(32767n);
      expect(minI16.toBigInt()).toBe(-32768n);
      expect(maxI16.toBigInt()).toBe(32767n);
    });

    test('should maintain consistency across methods', () => {
      const values = [-32768, -1000, 0, 1000, 32767];
      values.forEach((val) => {
        const i16 = new CairoInt16(val);
        const bigintVal = i16.toBigInt();
        const hexVal = i16.toHexString();
        const apiRequest = i16.toApiRequest();

        expect(bigintVal).toBe(BigInt(val));
        // For negative values, hex uses field element representation
        if (val < 0) {
          const fieldElement = PRIME + BigInt(val);
          expect(hexVal).toBe(`0x${fieldElement.toString(16)}`);
        } else {
          expect(hexVal).toBe(`0x${val.toString(16)}`);
        }
        // apiRequest should equal hexVal
        expect(apiRequest[0]).toBe(hexVal);
      });
    });

    test('should preserve exact values without precision loss', () => {
      const testValues = [-32768, -12345, 0, 12345, 32767];
      testValues.forEach((val) => {
        const i16 = new CairoInt16(val);
        expect(i16.toBigInt()).toBe(BigInt(val));
        expect(Number(i16.toBigInt())).toBe(val);
      });
    });
  });
});
